using System.Collections;
using System.IO;

public class MidiSequence
{
    public string name;
    public static bool debug = false;
    private Hashtable buffer = null;
    private int currentChannel = -1;
    private bool multitracks = false;
    public bool multinotes = false;
    private int nbTracks = 0;
    public Hashtable infos = new Hashtable();
    private Hashtable currentTrackModifier = null;
    public ArrayList tracks = new ArrayList();
    public Hashtable tempo = new Hashtable();
    private float currentTime = 0;

    public MidiSequence()
    {
    }

    public int getNbTracks()
    {
        if (nbTracks == 0)
        {
            nbTracks = tracks.Count;
        }
        return nbTracks;
    }

    public MidiSequence parse(string tmidPath)
    {
        if (debug) UnityEngine.MonoBehaviour.print("parsing of sound file : " + tmidPath);
        FileInfo tmidFile = new FileInfo(tmidPath);
        this.name = tmidFile.Name.Replace(".tmid","");
        StreamReader reader;
        if (debug) UnityEngine.MonoBehaviour.print("open file " + tmidFile.FullName);
        reader = tmidFile.OpenText();
        string line = reader.ReadLine();
        while (line != null)
        {
            int tracksLength = tracks.Count;
            MidiLine midiLine = new MidiLine();
            int type = midiLine.parse(line);
            //UnityEngine.MonoBehaviour.print(midiLine.getLine());
            Hashtable track;
            Hashtable trackInfos;
            ArrayList trackNotes;
            float newPitch;
            int newChannel;
            switch (type)
            {
                case MidiLine.INCONSISTENT:
                    break;

                case MidiLine.HEADER_CHUNK:
                    multitracks = (bool)midiLine.getInfos()["multitracks"];
                    if (multitracks)
                        if(debug) UnityEngine.MonoBehaviour.print("Multitracks sound file : " + (int)midiLine.getInfos()["nbTracks"] + " tracks");

                    break;

                case MidiLine.INFO_GENERAL:
                    infos[midiLine.getInfos()["name"]] = midiLine.getInfos()["value"];
                    if (debug) UnityEngine.MonoBehaviour.print("Info : " + midiLine.getInfos()["name"] + " = " + midiLine.getInfos()["value"]);

                    break;

                case MidiLine.BEGIN_TRACK:
                    track = new Hashtable();
                    trackInfos = new Hashtable();
                    currentTrackModifier = new Hashtable();
                    buffer = new Hashtable();
                    currentChannel = -1;
                    currentTime = 0;
                    trackInfos["channel"] = currentChannel;
                    track["infos"] = trackInfos;
                    track["notes"] = new ArrayList();
                    tracks.Add(track);
                    if (debug) UnityEngine.MonoBehaviour.print("BEGIN TRACK (nb track = " + tracks.Count + ")");

                    break;

                case MidiLine.END_TRACK:
                    track = (Hashtable)tracks[tracksLength-1];
                    trackInfos = (Hashtable)track["infos"];
                    if ((int)trackInfos["channel"] < 0)
                    {
                        tracks.RemoveAt(tracksLength - 1);
                        if (debug) UnityEngine.MonoBehaviour.print("END OF TRACK : the track is void (nb track = " + tracks.Count + ")");
                    }
                    else
                    {
                        if (debug) UnityEngine.MonoBehaviour.print("END OF TRACK " + (int)trackInfos["channel"] + " (nb track = " + tracks.Count + ")");
                    }
                    if (debug) UnityEngine.MonoBehaviour.print("=================================================");
                    break;

                case MidiLine.TRACK_MODIFIER:
                    newChannel = (int)midiLine.getInfos()["channel"];
                    if (currentChannel >= 0 && currentChannel != newChannel)
                        UnityEngine.Debug.LogWarning("ch" + newChannel + " is not the current channel (" + midiLine.getLine() + ")");
                    else if(currentTrackModifier == null)
                        UnityEngine.Debug.LogWarning("modifier without track beginned (" + midiLine.getLine() + ")");
                    else
                    {
                        string name = (string)midiLine.getInfos()["name"];
                        currentTrackModifier[name] = (int)midiLine.getInfos()["value"];
                        if (debug) UnityEngine.MonoBehaviour.print("Modifier ( channel=" + newChannel + " ; " + name + "=" + currentTrackModifier[name] + " )");
                    }

                    break;

                case MidiLine.TEMPO:
                    tempo[(float)midiLine.getInfos()["time"]] = (int)midiLine.getInfos()["value"];
                    if (MidiSequence.debug) UnityEngine.MonoBehaviour.print("Tempo ( time=" + midiLine.getInfos()["time"] + " ; bpm=" + midiLine.getInfos()["value"] + " )");
                    break;

                case MidiLine.NOTE_IN:
                    track = (Hashtable)tracks[tracksLength - 1];
                    trackInfos = (Hashtable)track["infos"];
                    newChannel = (int)midiLine.getInfos()["channel"];
                    if ((int)trackInfos["channel"] >= 0 && (int)trackInfos["channel"] != newChannel)
                        UnityEngine.Debug.LogWarning("ch" + newChannel + " is not the current channel");
                    else
                    {
                        currentTime += (float)midiLine.getInfos()["time"];
                        float time = currentTime;
                        midiLine.getInfos()["time"] = time;
                        if (buffer.ContainsKey(midiLine.getInfos()["pitch"]))
                        {
                            if (debug) UnityEngine.Debug.LogWarning("the note " + midiLine.getInfos()["pitch"] + " of ch" + newChannel + " has already started");
                        }
                        else
                        {
                            buffer[midiLine.getInfos()["pitch"]] = midiLine.getInfos();
                        }
                        currentChannel = newChannel;
                        trackInfos["channel"] = currentChannel;
                    }

                    break;

                case MidiLine.NOTE_OUT:
                    newChannel = (int)midiLine.getInfos()["channel"];
                    newPitch = (float)midiLine.getInfos()["pitch"];
                    if (currentChannel != newChannel)
                    {
                        UnityEngine.Debug.LogWarning("ch" + newChannel + " is not the current channel");
                    }
                    else
                    {
                        track = (Hashtable)tracks[tracksLength - 1];
                        trackNotes = (ArrayList)track["notes"];
                        if (buffer.ContainsKey(newPitch))
                        {
                            currentTime += (float)midiLine.getInfos()["time"];
                            midiLine.getInfos()["time"] = currentTime;
                            Note newNote = new Note((Hashtable)buffer[newPitch], midiLine.getInfos(), currentTrackModifier);
                            trackNotes.Add(newNote);
                            buffer.Remove(newPitch);
                            if (debug) UnityEngine.MonoBehaviour.print(newNote);

                            checkMultinote(tracksLength - 1, newNote);
                        }
                        else
                        {
                            if (debug) UnityEngine.Debug.LogWarning("the note of ch" + newChannel + " has not started");
                        }
                    }

                    break;

                case MidiLine.TRACK_INFO:
                    track = (Hashtable)tracks[tracksLength - 1];
                    trackInfos = (Hashtable)track["infos"];
                    trackInfos[midiLine.getInfos()["name"]] = midiLine.getInfos()["value"];
                    if (debug) UnityEngine.MonoBehaviour.print("Trackinfo ( " + midiLine.getInfos()["name"] + "=" + midiLine.getInfos()["value"] + ")");

                    break;
            }
            line = reader.ReadLine();
        }
        reader.Close();
        return this;
    }

    void checkMultinote(int numTrack, Note currentNote)
    {
        if (multinotes) return;
        Hashtable track = (Hashtable)tracks[numTrack];
        ArrayList notes = (ArrayList)track["notes"];
        int i = notes.Count - 2;
        while (i >= 0 && !multinotes && (((Note)notes[i]).startTime + ((Note)notes[i]).length) > currentNote.startTime)
        {
            Note note = (Note)notes[i];
            multinotes = currentNote.isOverlap(note);
            UnityEngine.MonoBehaviour.print("Compare : " + name);
            UnityEngine.MonoBehaviour.print("current :\t"+currentNote);
            UnityEngine.MonoBehaviour.print("with :\t"+note);
            if (multinotes)
                UnityEngine.MonoBehaviour.print("OVERLAP");
            i--;
        }
    }

    public override string ToString()
    {
        string result = "MIDI SEQUENCE\n";
        result += "General infos : \n";
        foreach (string key in infos.Keys)
            result += "\t" + key + "=" + infos[key] + "\n";
        result += "Tempos : \n";
        foreach (float key in tempo.Keys)
            result += "\t" + key + ":" + tempo[key] + "\n";
        foreach (Hashtable track in tracks)
        {
            result += "--------- \n";
            result += "Track : \n";
            Hashtable infos = (Hashtable)track["infos"];
            foreach (string key in infos.Keys)
            {
                result += "\t" + key + "=" + infos[key] + "\n";
            }
            result += "\nNotes : \n";
            ArrayList notes = (ArrayList)track["notes"];
            foreach (Note note in notes)
            {
                result += "\t" + note.ToString() + "\n";
            }
        }
        return result;
    }
}
